package trikita.talalarmo;

import android.view.View;
import android.view.MotionEvent;
import android.content.Context;

import android.util.AttributeSet;
import android.content.res.TypedArray;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Typeface;

import java.util.Calendar;

import android.util.Log;

/** Represents hour or minute clock */
public class ClockView extends View {
	private final static String tag = "ClockView";

	private Context mContext;

	public final static int BLACK = 0xff000000;
	public final static int WHITE = 0xffffffff;
	public final static int NEON = 0xff00FFFF;

	enum TypeSetting {
		NONE, HOUR, MINUTE, AMPM
	};
	TypeSetting typeSetting = TypeSetting.NONE;

	enum DayNight {
		AM, PM
	};
	DayNight dayNight = DayNight.AM;

	/** (mOuterRadius - mInnerRadius) = arc thickness || mAttrWidth */
	private float mOuterRadiusHour, mOuterRadiusMin;
	private float mInnerRadiusHour, mInnerRadiusMin;
	/** Arrow small cirle radius */
	private float mArrowRadius, mArrowRadiusBIG;
	/** A clock arrow center is always kept on this radius */
	private float mMiddleRadiusHour, mMiddleRadiusMin;
	/** Paint of the clock arc where the arrow is drawn */
	private Paint mOuterPaintHour = new Paint();
	private Paint mOuterPaintMin = new Paint();
	/** Paint of the inner empty area and the clock arrow */
	private Paint mInnerPaintHour = new Paint();
	private Paint mInnerPaintMin = new Paint();
	/** Paint the clock arrow */
	private Paint mArrowPaintHour = new Paint();
	private Paint mArrowPaintMin = new Paint();

	/** An absolute center of the ClockView */
	private PointF mCenter;
	/** A clock arrow center */
	private PointF mArrowPositionHour;
	private PointF mArrowPositionMin;

	private int mTimeHour, mTimeMin;
	private Typeface mTypeface;
	private Paint mTextPaint = new Paint();
	private Paint mTextPaintAmPm = new Paint();

	private Paint mTextPaintHour = new Paint();
	private Paint mTextPaintMin = new Paint();

	/** 12 for the hour ClockView and 59 for minute one */
	private int mMaxValueHour, mMaxValueMin;
	/** Snap to 5 minutes for minutes clock */
	private boolean mSnap = true;

	private boolean mStartInit = false;

	/**
	 * The limit area that starts at the arrow center. Outwards this area we
	 * stop to handle touch events
	 */
	private float mTouchSensitiveWidth;
	/** Arc thickness */
	private float mAttrWidth;

	private OnAlarmSetListener mOnAlarmSetListener;

	private int textSizeHoursMinutes = 18;
	private int textSizeHoursMinutesBIG = 36;

	public interface OnAlarmSetListener {
		public void onSetTime(ClockView v, int timeHour, int timeMin);
		public void onProceedSetTime();
		public void switchAMPM();
	}

	public ClockView(Context context) {
		this(context, null, 0);
	}

	public ClockView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public ClockView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		// Retrieve custom xml attributes of the ClockView
		TypedArray a = context.obtainStyledAttributes(attrs,
				R.styleable.ClockView);

		final int cnt = a.getIndexCount();
		for (int i = 0; i < cnt; ++i) {
			int attr = a.getIndex(i);
			switch (attr) {
				case R.styleable.ClockView_width :
					mAttrWidth = a.getDimension(attr, 0);
					mTouchSensitiveWidth = mAttrWidth / 4;
					break;
				case R.styleable.ClockView_touchSensitiveWidth :
					mTouchSensitiveWidth = a.getDimension(attr, 0);
					break;
				case R.styleable.ClockView_radius :
					mMiddleRadiusMin = a.getDimension(attr, 0);
					break;
				case R.styleable.ClockView_maxValueHour :
					mMaxValueHour = a.getInt(attr, 0);
					break;
				case R.styleable.ClockView_maxValueMin :
					mMaxValueMin = a.getInt(attr, 0);
					break;
				case R.styleable.ClockView_textSize :
					mTextPaint.setTextSize(a.getDimensionPixelSize(attr, 0));
					mTextPaintAmPm
							.setTextSize(a.getDimensionPixelSize(attr, 0) / 3);
					break;
			}
		}
		a.recycle();

		mTypeface = Typeface.createFromAsset(context.getAssets(),
				"fonts/font.ttf");
		mTextPaint.setTypeface(mTypeface);
		mTextPaintAmPm.setTypeface(mTypeface);
		mTextPaintHour.setTypeface(mTypeface);
		mTextPaintMin.setTypeface(mTypeface);

		mTextPaint.setColor(BLACK);
		mTextPaint.setTextAlign(Paint.Align.CENTER);
		mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);

		mTextPaintAmPm.setColor(BLACK);
		mTextPaintAmPm.setTextAlign(Paint.Align.CENTER);
		mTextPaintAmPm.setFlags(Paint.ANTI_ALIAS_FLAG);

		mTextPaintMin.setColor(BLACK);
		mTextPaintMin.setTextAlign(Paint.Align.CENTER);
		mTextPaintMin.setFlags(Paint.ANTI_ALIAS_FLAG);

		mTextPaintHour.setColor(BLACK);
		mTextPaintHour.setTextAlign(Paint.Align.CENTER);
		mTextPaintHour.setFlags(Paint.ANTI_ALIAS_FLAG);

		mOuterPaintHour.setColor(BLACK);
		mOuterPaintMin.setColor(BLACK);
		mInnerPaintHour.setColor(WHITE);
		mInnerPaintMin.setColor(WHITE);
		mArrowPaintHour.setColor(WHITE);
		mArrowPaintMin.setColor(WHITE);
		mOuterPaintHour.setFlags(Paint.ANTI_ALIAS_FLAG);
		mOuterPaintMin.setFlags(Paint.ANTI_ALIAS_FLAG);
		mInnerPaintHour.setFlags(Paint.ANTI_ALIAS_FLAG);
		mInnerPaintMin.setFlags(Paint.ANTI_ALIAS_FLAG);
		mArrowPaintHour.setFlags(Paint.ANTI_ALIAS_FLAG);
		mArrowPaintMin.setFlags(Paint.ANTI_ALIAS_FLAG);

		// By default - set current time for ClockView
		Calendar current = Calendar.getInstance();
		current.setTimeInMillis(System.currentTimeMillis());
		mTimeHour = current.get(Calendar.HOUR);
		mTimeMin = current.get(Calendar.MINUTE);
	}

	@Override
	protected void onDraw(Canvas c) {
		init();

		drawMinutesCircles(c);

		drawMinutesText(c);

		drawHoursCircles(c);

		drawHoursText(c);

		drawArrowMinutes(c);

		drawArrowHours(c);

		drawArrowMinutesText(c);

		drawArrowHourText(c);

		drawTime(c);

	}

	private void drawTime(Canvas c) {
		// Create bitmap with input text
		Rect r = new Rect();
		String text = null;

		if (mTimeMin >= 10)
			text = "" + mTimeHour + ":" + mTimeMin;
		else
			text = "" + mTimeHour + ":0" + mTimeMin;

		String zero = "0:00";
		text = (text.length() > 1 ? text : zero.concat(text));
		mTextPaint.getTextBounds(text, 0, text.length(), r);
		c.drawText(text, mCenter.x, mCenter.y + r.height() / 2, mTextPaint);

		// Create bitmap with input text
		r = new Rect();
		text = dayNight.toString();

		text = (text.length() > 1 ? text : zero.concat(text));
		mTextPaintAmPm.getTextBounds(text, 0, text.length(), r);
		c.drawText(text, mCenter.x, mCenter.y + r.height() / 2 + mArrowRadius
				* 3, mTextPaintAmPm);
	}

	private void drawArrowHourText(Canvas c) {
		// write HOUR into circle arrow
		Rect r = new Rect();
		String text = "" + mTimeHour;
		mTextPaintHour.getTextBounds(text, 0, text.length(), r);
		c.drawText(text, mArrowPositionHour.x,
				mArrowPositionHour.y + r.height() / 2, mTextPaintHour);
	}

	private void drawArrowMinutesText(Canvas c) {
		// write MIN into circle arrow
		Rect r = new Rect();
		String text = "" + mTimeMin;
		mTextPaintMin.getTextBounds(text, 0, text.length(), r);
		c.drawText(text, mArrowPositionMin.x, mArrowPositionMin.y + r.height()
				/ 2, mTextPaintMin);
	}

	private void drawArrowHours(Canvas c) {
		if (typeSetting == TypeSetting.HOUR) {

			mTextPaintHour.setTextSize(textSizeHoursMinutesBIG);
			// Draw arrow circle
			c.drawCircle(mArrowPositionHour.x, mArrowPositionHour.y,
					mArrowRadiusBIG, mArrowPaintHour);

		} else {
			mTextPaintHour.setTextSize(textSizeHoursMinutes);
			// Draw arrow circle
			c.drawCircle(mArrowPositionHour.x, mArrowPositionHour.y,
					mArrowRadius, mArrowPaintHour);
		}
	}

	private void drawArrowMinutes(Canvas c) {
		if (typeSetting == TypeSetting.MINUTE) {
			mTextPaintMin.setTextSize(textSizeHoursMinutesBIG);
			// Draw arrow circle
			c.drawCircle(mArrowPositionMin.x, mArrowPositionMin.y,
					mArrowRadiusBIG, mArrowPaintMin);
		} else {
			mTextPaintMin.setTextSize(textSizeHoursMinutes);
			// Draw arrow circle
			c.drawCircle(mArrowPositionMin.x, mArrowPositionMin.y,
					mArrowRadius, mArrowPaintMin);
		}
	}

	private void drawHoursCircles(Canvas c) {
		// Draw the larger circle at first
		c.drawCircle(mCenter.x, mCenter.y, mOuterRadiusHour, mOuterPaintHour);
		// and the smaller one after that.
		c.drawCircle(mCenter.x, mCenter.y, mInnerRadiusHour, mInnerPaintHour);
	}

	private void drawMinutesCircles(Canvas c) {
		// Draw the larger circle at first
		c.drawCircle(mCenter.x, mCenter.y, mOuterRadiusMin, mOuterPaintMin);
		// and the smaller one after that.
		c.drawCircle(mCenter.x, mCenter.y, mInnerRadiusMin, mInnerPaintMin);
	}

	private void drawHoursText(Canvas c) {
		for (int i = 1; i <= mMaxValueHour; i++) {
			drawHourMinute(c, i, mMaxValueHour, mMiddleRadiusHour);
		}

	}

	private void drawMinutesText(Canvas c) {
		for (int i = 0; i < mMaxValueMin; i++) {
			if (i % 5 == 0)
				drawHourMinute(c, i, mMaxValueMin, mMiddleRadiusMin);
		}
	}

	private void drawHourMinute(Canvas c, int i, int maxValue,
			float mMiddleRadius) {
		Rect r = new Rect();
		String text = "" + i;
		Paint p = getHourMinutePaint(i, maxValue);
		p.getTextBounds(text, 0, text.length(), r);
		// c.drawText(text, mCenter.x, mCenter.y + r.height() / 2, p);

		double angle = (360 / maxValue) * i;
		int dx = (int) (mMiddleRadius * Math.sin(Math.toRadians(angle)));
		int dy = (int) (mMiddleRadius * Math.cos(Math.toRadians(angle)));
		float x = mCenter.x + dx;
		float y = mCenter.y - dy;

		c.drawText(text, x, y + r.height() / 2, p);
	}

	private Paint getHourMinutePaint(int i, int maxhours) {
		Paint mTextPaintHoursMinutes = new Paint();
		mTextPaintHoursMinutes.setTextSize(textSizeHoursMinutes);
		mTextPaintHoursMinutes.setTypeface(mTypeface);

		mTextPaintHoursMinutes
				.setColor(dayNight == DayNight.AM ? WHITE : BLACK);

		mTextPaintHoursMinutes.setTextAlign(Paint.Align.CENTER);
		mTextPaintHoursMinutes.setFlags(Paint.ANTI_ALIAS_FLAG);

		return mTextPaintHoursMinutes;
	}

	/**
	 * Called just one time at the beginning to calculate the values of some
	 * class fields
	 */
	private void init() {
		if (mStartInit)
			return;
		mStartInit = true;

		int w = getWidth();
		int h = getHeight();

		mOuterRadiusMin = mMiddleRadiusMin + mAttrWidth / 2;
		mInnerRadiusMin = mMiddleRadiusMin - mAttrWidth / 2;

		mMiddleRadiusHour = mMiddleRadiusMin - (int) (mAttrWidth * 1.2);
		mOuterRadiusHour = mMiddleRadiusHour + mAttrWidth / 2;
		mInnerRadiusHour = mMiddleRadiusHour - mAttrWidth / 2;

		mArrowRadius = (float) (mAttrWidth * 0.4);
		mArrowRadiusBIG = mArrowRadius * 2;

		mCenter = new PointF(w / 2, h / 2);
		mArrowPositionMin = new PointF();
		mArrowPositionHour = new PointF();

		// Set clock arrow according to the current time value
		updateArrowPosition(mArrowPositionMin, mMiddleRadiusMin, mTimeMin,
				mMaxValueMin);
		updateArrowPosition(mArrowPositionHour, mMiddleRadiusHour, mTimeHour,
				mMaxValueHour);

	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		PointF p = new PointF(event.getX(), event.getY());

		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			mOnAlarmSetListener.onProceedSetTime();
		}

		/* ---------------------- DOWN AND MOVE ACTION ----------------------- */
		if (event.getAction() == MotionEvent.ACTION_DOWN
				|| event.getAction() == MotionEvent.ACTION_MOVE) {

			double touchRadius = Math
					.sqrt((p.x - mCenter.x) * (p.x - mCenter.x)
							+ (p.y - mCenter.y) * (p.y - mCenter.y));

			// Detect if the move action is inside the sensitive area around the
			// circle

			if (touchRadius > (mOuterRadiusMin + mTouchSensitiveWidth)) {
				return false;

			} else if (event.getAction() == MotionEvent.ACTION_DOWN
					&& touchRadius < (mInnerRadiusHour - mTouchSensitiveWidth)) {
				typeSetting = TypeSetting.AMPM;
				return true;

			} else if (touchRadius <= (mOuterRadiusHour + mTouchSensitiveWidth)
					&& touchRadius >= (mInnerRadiusHour - mTouchSensitiveWidth)
					&& (typeSetting == TypeSetting.NONE || typeSetting == TypeSetting.HOUR)) {
				// HOUR SET

				typeSetting = TypeSetting.HOUR;

				mArrowPaintHour.setColor(NEON);

				mArrowPositionHour.x = (float) (((mMiddleRadiusHour * (p.x - mCenter.x)) / touchRadius) + mCenter.x);
				mArrowPositionHour.y = (float) (((mMiddleRadiusHour * (p.y - mCenter.y)) / touchRadius) + mCenter.y);

				mTimeHour = updateTime(mArrowPositionHour, mMaxValueHour);
				updateArrowPosition(mArrowPositionHour, mMiddleRadiusHour,
						mTimeHour, mMaxValueHour);

				invalidate();
				return true;

			} else if (touchRadius <= (mOuterRadiusMin + mTouchSensitiveWidth)
					&& touchRadius >= (mInnerRadiusMin - mTouchSensitiveWidth)
					&& (typeSetting == TypeSetting.NONE || typeSetting == TypeSetting.MINUTE)) {
				// MIN SET

				typeSetting = TypeSetting.MINUTE;

				mArrowPaintMin.setColor(NEON);

				mArrowPositionMin.x = (float) (((mMiddleRadiusMin * (p.x - mCenter.x)) / touchRadius) + mCenter.x);
				mArrowPositionMin.y = (float) (((mMiddleRadiusMin * (p.y - mCenter.y)) / touchRadius) + mCenter.y);

				mTimeMin = updateTime(mArrowPositionMin, mMaxValueMin);
				updateArrowPosition(mArrowPositionMin, mMiddleRadiusMin,
						mTimeMin, mMaxValueMin);

				invalidate();
				return true;
			}
		}

		/* ---------------------- UP ACTION ----------------------------- */
		if (event.getAction() == MotionEvent.ACTION_UP
				&& typeSetting == TypeSetting.AMPM) {
			typeSetting = TypeSetting.NONE;

			setChecked(dayNight != DayNight.AM);

			invalidate();
			return true;
		}
		if (event.getAction() == MotionEvent.ACTION_UP) {
			Log.d(tag, "ACTION_UP");

			typeSetting = TypeSetting.NONE;

			mArrowPaintHour.setColor(mInnerPaintHour.getColor());
			mArrowPaintMin.setColor(mInnerPaintMin.getColor());

			if (mOnAlarmSetListener != null) {
				// The time is set now
				mTimeMin = updateTime(mArrowPositionMin, mMaxValueMin);
				updateArrowPosition(mArrowPositionMin, mMiddleRadiusMin,
						mTimeMin, mMaxValueMin);
				mTimeHour = updateTime(mArrowPositionHour, mMaxValueHour);
				updateArrowPosition(mArrowPositionHour, mMiddleRadiusHour,
						mTimeHour, mMaxValueHour);

				mOnAlarmSetListener.onSetTime(this, mTimeHour, mTimeMin);
			}

			invalidate();
			return true;
		}

		/* --------------------- CANCEL ACTION -------------------------- */
		if (event.getAction() == MotionEvent.ACTION_CANCEL) {
			Log.d(tag, "CANCEL");
		}

		return false;
	}

	/** Calculates time by mArrowPosition coordinates */
	private int updateTime(PointF mArrowPosition, int mMaxValue) {
		PointF p = mArrowPosition;
		int mTime = 0;

		double dx = mCenter.x - p.x;
		double dy = mCenter.y - p.y;
		// The unknown coefficients are here to offset android radial angles
		// so that the angle of the 0 degrees will be at the top
		double d = (Math.atan2(dy, dx) + 3 * Math.PI / 2) % (Math.PI * 2);

		if (mMaxValue > 12) { // for minute ClockView
			if (mSnap) {
				mTime = (int) (Math.round(d * mMaxValue / (Math.PI * 2)));// /
																			// 5)
																			// *
																			// 5);
			} else {
				mTime = (int) Math.round(d * mMaxValue / (Math.PI * 2));
			}
			if (mTime == 60)
				mTime = 0;
		} else { // for hour ClockView
			mTime = (int) Math.round(d * mMaxValue / (Math.PI * 2));
			if (mTime == 0)
				mTime = mMaxValue;
		}
		return mTime;
	}

	/** Calculates mArrowPosition coordinates by the time value */
	private void updateArrowPosition(PointF mArrowPosition,
			float mMiddleRadius, int mTime, int mMaxValue) {
		double angle = (Math.PI * 2 * mTime) / mMaxValue;
		// The unknown coefficients are here to offset android radial angles
		// so that the angle of the 0 degrees will be at the top
		angle = (3 * Math.PI / 2 + angle) % (2 * Math.PI);

		mArrowPosition.set(mCenter.x, mCenter.y);
		mArrowPosition.offset((int) (Math.cos(angle) * mMiddleRadius),
				(int) (Math.sin(angle) * mMiddleRadius));
	}

	/**
	 * When the user toggles am/pm option this method has to invert colors for
	 * the certain parts of ClockView
	 */
	public void updateColors(int mainColor) {
		mOuterPaintHour.setColor(mainColor);
		mOuterPaintMin.setColor(mainColor);
		mInnerPaintHour.setColor(mainColor ^ 0xffffff);
		mInnerPaintMin.setColor(mainColor ^ 0xffffff);
		mArrowPaintHour.setColor(mInnerPaintHour.getColor());
		mArrowPaintMin.setColor(mInnerPaintMin.getColor());
		mTextPaint.setColor(mainColor);
		mTextPaintAmPm.setColor(mainColor);
		mTextPaintHour.setColor(mainColor);
		mTextPaintMin.setColor(mainColor);
	}

	public void setOnAlarmSetListener(OnAlarmSetListener stl) {
		mOnAlarmSetListener = stl;
	}

	public void setTime(int timeHour, int timeMin) {
		mTimeHour = timeHour;
		if (mTimeHour == 0)
			mTimeHour = 12;

		mTimeMin = timeMin;

		invalidate();
	}

	public void setChecked(boolean isAM) {
		if (isAM)
			dayNight = DayNight.AM;
		else
			dayNight = DayNight.PM;

		updateColors(isAM ? BLACK : WHITE);
		mOnAlarmSetListener.switchAMPM();
	}

	public boolean isChecked() {
		return dayNight == DayNight.AM;
	}

	public int[] getTime() {
		return new int[]{mTimeHour, mTimeMin};
	}

	public void setSnap(boolean snap) {
		mSnap = snap;
	}
}
